// from https://zhuanlan.zhihu.com/p/28800368

// include the basic windows header file
#define UNICODE
#include <windows.h>
#include <windowsx.h>
#include <tchar.h>
#include <GL/gl.h>
#include <fstream>

#include "../../Framework/Common/vectormath.hpp"

/////////////
// DEFINES //
/////////////

#define WGL_DRAW_TO_WINDOW_ARB                  0x2001
#define WGL_ACCELERATION_ARB                    0x2003
#define WGL_SWAP_METHOD_ARB                     0x2007
#define WGL_SUPPORT_OPENGL_ARB                  0x2010
#define WGL_DOUBLE_BUFFER_ARB                   0x2011
#define WGL_PIXEL_TYPE_ARB                      0x2013
#define WGL_COLOR_BITS_ARB                      0x2014
#define WGL_DEPTH_BITS_ARB                      0x2022
#define WGL_STENCIL_BITS_ARB                    0x2023
#define WGL_FULL_ACCELERATION_ARB               0x2027
#define WGL_SWAP_EXCHANGE_ARB                   0x2028
#define WGL_TYPE_RGBA_ARB                       0x202B
#define WGL_CONTEXT_MAJOR_VERSION_ARB           0x2091
#define WGL_CONTEXT_MINOR_VERSION_ARB           0x2092
#define GL_ARRAY_BUFFER                         0x8892
#define GL_STATIC_DRAW                          0x88E4
#define GL_FRAGMENT_SHADER                      0x8B30
#define GL_VERTEX_SHADER                        0x8B31
#define GL_COMPILE_STATUS                       0x8B81
#define GL_LINK_STATUS                          0x8B82
#define GL_INFO_LOG_LENGTH                      0x8B84
#define GL_TEXTURE0                             0x84C0
#define GL_BGRA                                 0x80E1
#define GL_ELEMENT_ARRAY_BUFFER                 0x8893

//////////////
// TYPEDEFS //
//////////////
typedef BOOL (WINAPI * PFNWGLCHOOSEPIXELFORMATARBPROC) (HDC hdc, const int *piAttribIList, const FLOAT *pfAttribFList, UINT nMaxFormats, 
                                                        int *piFormats, UINT *nNumFormats);
typedef HGLRC (WINAPI * PFNWGLCREATECONTEXTATTRIBSARBPROC) (HDC hDC, HGLRC hShareContext, const int *attribList);
typedef BOOL (WINAPI * PFNWGLSWAPINTERVALEXTPROC) (int interval);
typedef void (APIENTRY * PFNGLATTACHSHADERPROC) (GLuint program, GLuint shader);
typedef void (APIENTRY * PFNGLBINDBUFFERPROC) (GLenum target, GLuint buffer);
typedef void (APIENTRY * PFNGLBINDVERTEXARRAYPROC) (GLuint array);
typedef void (APIENTRY * PFNGLBUFFERDATAPROC) (GLenum target, ptrdiff_t size, const GLvoid *data, GLenum usage);
typedef void (APIENTRY * PFNGLCOMPILESHADERPROC) (GLuint shader);
typedef GLuint (APIENTRY * PFNGLCREATEPROGRAMPROC) (void);
typedef GLuint (APIENTRY * PFNGLCREATESHADERPROC) (GLenum type);
typedef void (APIENTRY * PFNGLDELETEBUFFERSPROC) (GLsizei n, const GLuint *buffers);
typedef void (APIENTRY * PFNGLDELETEPROGRAMPROC) (GLuint program);
typedef void (APIENTRY * PFNGLDELETESHADERPROC) (GLuint shader);
typedef void (APIENTRY * PFNGLDELETEVERTEXARRAYSPROC) (GLsizei n, const GLuint *arrays);
typedef void (APIENTRY * PFNGLDETACHSHADERPROC) (GLuint program, GLuint shader);
typedef void (APIENTRY * PFNGLENABLEVERTEXATTRIBARRAYPROC) (GLuint index);
typedef void (APIENTRY * PFNGLGENBUFFERSPROC) (GLsizei n, GLuint *buffers);
typedef void (APIENTRY * PFNGLGENVERTEXARRAYSPROC) (GLsizei n, GLuint *arrays);
typedef GLint (APIENTRY * PFNGLGETATTRIBLOCATIONPROC) (GLuint program, const char *name);
typedef void (APIENTRY * PFNGLGETPROGRAMINFOLOGPROC) (GLuint program, GLsizei bufSize, GLsizei *length, char *infoLog);
typedef void (APIENTRY * PFNGLGETPROGRAMIVPROC) (GLuint program, GLenum pname, GLint *params);
typedef void (APIENTRY * PFNGLGETSHADERINFOLOGPROC) (GLuint shader, GLsizei bufSize, GLsizei *length, char *infoLog);
typedef void (APIENTRY * PFNGLGETSHADERIVPROC) (GLuint shader, GLenum pname, GLint *params);
typedef void (APIENTRY * PFNGLLINKPROGRAMPROC) (GLuint program);
typedef void (APIENTRY * PFNGLSHADERSOURCEPROC) (GLuint shader, GLsizei count, const char* *string, const GLint *length);
typedef void (APIENTRY * PFNGLUSEPROGRAMPROC) (GLuint program);
typedef void (APIENTRY * PFNGLVERTEXATTRIBPOINTERPROC) (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, 
                                                        const GLvoid *pointer);
typedef void (APIENTRY * PFNGLBINDATTRIBLOCATIONPROC) (GLuint program, GLuint index, const char *name);
typedef GLint (APIENTRY * PFNGLGETUNIFORMLOCATIONPROC) (GLuint program, const char *name);
typedef void (APIENTRY * PFNGLUNIFORMMATRIX4FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
typedef void (APIENTRY * PFNGLACTIVETEXTUREPROC) (GLenum texture);
typedef void (APIENTRY * PFNGLUNIFORM1IPROC) (GLint location, GLint v0);
typedef void (APIENTRY * PFNGLGENERATEMIPMAPPROC) (GLenum target);
typedef void (APIENTRY * PFNGLDISABLEVERTEXATTRIBARRAYPROC) (GLuint index);
typedef void (APIENTRY * PFNGLUNIFORM3FVPROC) (GLint location, GLsizei count, const GLfloat *value);
typedef void (APIENTRY * PFNGLUNIFORM4FVPROC) (GLint location, GLsizei count, const GLfloat *value);


PFNGLATTACHSHADERPROC glAttachShader;
PFNGLBINDBUFFERPROC glBindBuffer;
PFNGLBINDVERTEXARRAYPROC glBindVertexArray;
PFNGLBUFFERDATAPROC glBufferData;
PFNGLCOMPILESHADERPROC glCompileShader;
PFNGLCREATEPROGRAMPROC glCreateProgram;
PFNGLCREATESHADERPROC glCreateShader;
PFNGLDELETEBUFFERSPROC glDeleteBuffers;
PFNGLDELETEPROGRAMPROC glDeleteProgram;
PFNGLDELETESHADERPROC glDeleteShader;
PFNGLDELETEVERTEXARRAYSPROC glDeleteVertexArrays;
PFNGLDETACHSHADERPROC glDetachShader;
PFNGLENABLEVERTEXATTRIBARRAYPROC glEnableVertexAttribArray;
PFNGLGENBUFFERSPROC glGenBuffers;
PFNGLGENVERTEXARRAYSPROC glGenVertexArrays;
PFNGLGETATTRIBLOCATIONPROC glGetAttribLocation;
PFNGLGETPROGRAMINFOLOGPROC glGetProgramInfoLog;
PFNGLGETPROGRAMIVPROC glGetProgramiv;
PFNGLGETSHADERINFOLOGPROC glGetShaderInfoLog;
PFNGLGETSHADERIVPROC glGetShaderiv;
PFNGLLINKPROGRAMPROC glLinkProgram;
PFNGLSHADERSOURCEPROC glShaderSource;
PFNGLUSEPROGRAMPROC glUseProgram;
PFNGLVERTEXATTRIBPOINTERPROC glVertexAttribPointer;
PFNGLBINDATTRIBLOCATIONPROC glBindAttribLocation;
PFNGLGETUNIFORMLOCATIONPROC glGetUniformLocation;
PFNGLUNIFORMMATRIX4FVPROC glUniformMatrix4fv;
PFNGLACTIVETEXTUREPROC glActiveTexture;
PFNGLUNIFORM1IPROC glUniform1i;
PFNGLGENERATEMIPMAPPROC glGenerateMipmap;
PFNGLDISABLEVERTEXATTRIBARRAYPROC glDisableVertexAttribArray;
PFNGLUNIFORM3FVPROC glUniform3fv;
PFNGLUNIFORM4FVPROC glUniform4fv;

PFNWGLCHOOSEPIXELFORMATARBPROC wglChoosePixelFormatARB;
PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB;
PFNWGLSWAPINTERVALEXTPROC wglSwapIntervalEXT;

typedef struct VertexType
{
    VectorType postion;
    VectorType color;
} VertexType;

HDC     g_deviceContext = 0;
HGLRC   g_renderingContext = 0;
char    g_videoCardDescription[128];

const bool VSYNC_ENABLED = true;
const float SCREEN_DEPTH = 1000.f;
const float SCREEN_NEAR = 0.1f;

int     g_vertexCount;
int     g_indexCount;

unsigned int g_vertexArrayId;
unsigned int g_vertexBufferId;
unsigned int g_indexBufferId;

unsigned int g_vertexShader;
unsigned int g_fragmentShader;
unsigned int g_shaderProgram;

const char VS_SHADER_SOURCE_FILE[] = "color.vs";
const char PS_SHADER_SOURCE_FILE[] = "color.ps";

float g_positionX = 0;
float g_positionY = 0;
float g_positionZ = -10;

float g_rotationX = 0;
float g_rotationY = 0;
float g_rotationZ = 0;

float g_worldMatrix[16];
float g_viewMatrix[16];
float g_projectionMatrix[16];

const WCHAR* szAppName = L"Hello, Engine!"; 

bool InitializeOpenGL(HWND hwnd, int screenWidth, int screenHeight, float screenDepth, float screenNear, bool vsync)
{
    int pixelFormat;
    unsigned int formatCount;
    int result;
    PIXELFORMATDESCRIPTOR pixelFormatDescriptor;
    float fieldOfView;
    float screenAspect;
    char* vendorString;
    char* rendererString;

    // get the device context for this window
    // 获取与窗口相关联的设备上下文（device context）
    g_deviceContext = GetDC(hwnd);
    if (!g_deviceContext) {
        return false;
    }

    pixelFormatDescriptor = {
        sizeof(PIXELFORMATDESCRIPTOR),    // size of this pfd  
        1,                                // version number  
        PFD_DRAW_TO_WINDOW |              // support window  
        PFD_SUPPORT_OPENGL |              // support OpenGL  
        PFD_DOUBLEBUFFER,                 // double buffered  
        PFD_TYPE_RGBA,                    // RGBA type  
        32,                               // 24-bit color depth  
        0, 0, 0, 0, 0, 0,                 // color bits ignored  
        0,                                // no alpha buffer  
        0,                                // shift bit ignored  
        0,                                // no accumulation buffer  
        0, 0, 0, 0,                       // accum bits ignored  
        32,                               // 32-bit z-buffer      
        0,                                // no stencil buffer  
        0,                                // no auxiliary buffer  
        PFD_MAIN_PLANE,                   // main layer  
        0,                                // reserved  
        0, 0, 0                           // layer masks ignored  
    };

    const int attributeListInt[] = {
        WGL_DRAW_TO_WINDOW_ARB, GL_TRUE, // support for rendering to a window
        WGL_SUPPORT_OPENGL_ARB, GL_TRUE, // support for OpenGL rendering
        WGL_DOUBLE_BUFFER_ARB, GL_TRUE,  // support for double buffer
        WGL_SWAP_METHOD_ARB, WGL_SWAP_EXCHANGE_ARB, // support for swapping front and back buffer
        WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB, // support for the RGBA pixel type
        WGL_ACCELERATION_ARB, WGL_FULL_ACCELERATION_ARB, // support for hardware acceleration
        WGL_COLOR_BITS_ARB, 32, // support for 32 bit color
        WGL_DEPTH_BITS_ARB, 24, // support for 24 depth buffer
        WGL_STENCIL_BITS_ARB, 8,
        0 // Null terminate the attribute list.
    };

    // query for a pixel format that fits the attributes we want.
    result = wglChoosePixelFormatARB(g_deviceContext, attributeListInt, NULL, 1, &pixelFormat, &formatCount);
    if (result != 1) {
        return false;
    }

    // if the vedio card/display can handle our desired pixel formal then we set it as the current one.
    result = SetPixelFormat(g_deviceContext, pixelFormat, &pixelFormatDescriptor);
    if (result != 1){
        return false;
    }

    // set the 4.0 Version of OpenGL in the attribute list
    const int attributeList[] = {
        WGL_CONTEXT_MAJOR_VERSION_ARB, 4,
        WGL_CONTEXT_MINOR_VERSION_ARB, 0,
        0 // null terminate the attribute list.
    };

    // create a OpenGL 4.0 rendering context.
    g_renderingContext = wglCreateContextAttribsARB(g_deviceContext, 0, attributeList);
    if (g_renderingContext == NULL) {
        return false;
    }

    // set the redering context to active.
    result = wglMakeCurrent(g_deviceContext, g_renderingContext);
    if (result != 1) {
        return false;
    }

    // set the depth buffer to be entirely cleared to 1.0 values.
    glClearDepth(1.0f);

    // enable depth testing
    glEnable(GL_DEPTH_TEST);

    // set teh polygon winding to front facing for the left handed system
    glFrontFace(GL_CW);

    // enable back face culling
    glEnable(GL_CULL_FACE);
    glCullFace(GL_BACK);

    // initialize the world/model matrix to the indentity matrix
    BuildIdentityMatrix(g_worldMatrix);

    // set the filed of view and screen aspect ratio
    fieldOfView = PI / 4.0f;
    screenAspect = (float) screenWidth / (float) screenHeight;

    // build the perspective projection matrix.
    BuildPerspectiveFovLHMatrix(g_projectionMatrix, fieldOfView, screenAspect, screenNear, screenDepth);

    // get the name of the video card.
    vendorString = (char*) glGetString(GL_VENDOR);
    rendererString = (char*) glGetString(GL_RENDER);

    // store the video card name in a class member variable so it can be retrieved later.
    if (vendorString) {
        strcpy_s(g_videoCardDescription, strlen(vendorString) + 1, vendorString);
    }
    if (rendererString) {
        strcpy_s(g_videoCardDescription, 2, " - ");
        strcpy_s(g_videoCardDescription, strlen(rendererString) + 1, rendererString);
    }

    // turn on or off the vertical sync depending on the input bool value.
    result = wglSwapIntervalEXT(vsync ? 1 : 0);
    // check if vsync was set correctly.
    if (result != 1) {
        return false;
    }
  

    return true;
}

bool LoadExtensionList()
{
    // load the OpenGL extensions that this application will be using.
    wglChoosePixelFormatARB = (PFNWGLCHOOSEPIXELFORMATARBPROC)wglGetProcAddress("wglChoosePixelFormatARB");
    if (!wglChoosePixelFormatARB) {
        return false;
    }

    wglCreateContextAttribsARB = (PFNWGLCREATECONTEXTATTRIBSARBPROC) wglGetProcAddress("wglCreateContextAttribsARB");
    if (!wglCreateContextAttribsARB) {
        return false;
    }

    wglSwapIntervalEXT = (PFNWGLSWAPINTERVALEXTPROC) wglGetProcAddress("wglSwapIntervalEXT");
    if (!wglSwapIntervalEXT) {
        return false;
    }

    glAttachShader = (PFNGLATTACHSHADERPROC) wglGetProcAddress("glAttachShader");
    if (!glAttachShader) {
        return false;
    }

    glBindBuffer = (PFNGLBINDBUFFERPROC) wglGetProcAddress("glBindBuffer");
    if (!glBindBuffer) {
        return false;
    }

    glBindVertexArray = (PFNGLBINDVERTEXARRAYPROC) wglGetProcAddress("glBindVertexArray");
    if (!glBindVertexArray) {
        return false;
    }

    glBufferData = (PFNGLBUFFERDATAPROC) wglGetProcAddress("glBufferData");
    if (!glBufferData) {
        return false;
    }

    glCompileShader = (PFNGLCOMPILESHADERPROC) wglGetProcAddress("glCompileShader");
    if (!glCompileShader) {
        return false;
    }

    glCreateProgram = (PFNGLCREATEPROGRAMPROC) wglGetProcAddress("glCreateProgram");
    if (!glCreateProgram) {
        return false;
    }

    glCreateShader = (PFNGLCREATESHADERPROC) wglGetProcAddress("glCreateShader");
    if (!glCreateShader) {
        return false;
    }

    glDeleteBuffers = (PFNGLDELETEBUFFERSPROC) wglGetProcAddress("glDeleteBuffers");
    if (!glDeleteBuffers) {
        return false;
    }

    glDeleteProgram = (PFNGLDELETEPROGRAMPROC) wglGetProcAddress("glDeleteProgram");
    if (!glDeleteProgram) {
        return false;
    }

    glDeleteShader = (PFNGLDELETESHADERPROC) wglGetProcAddress("glDeleteShader");
    if (!glDeleteShader) {
        return false;
    }

    glDeleteVertexArrays = (PFNGLDELETEVERTEXARRAYSPROC) wglGetProcAddress("glDeleteVertexArrays");
    if (!glDeleteVertexArrays) {
        return false;
    }

    glDetachShader = (PFNGLDETACHSHADERPROC) wglGetProcAddress("glDetachShader");
    if (!glDetachShader) {
        return false;
    }

    glEnableVertexAttribArray = (PFNGLENABLEVERTEXATTRIBARRAYPROC) wglGetProcAddress("glEnableVertexAttribArray");
    if (!glEnableVertexAttribArray) {
        return false;
    }

    glGenBuffers = (PFNGLGENBUFFERSPROC) wglGetProcAddress("glGenBuffers");
    if (!glGenBuffers) {
        return false;
    }

    glGenVertexArrays = (PFNGLGENVERTEXARRAYSPROC) wglGetProcAddress("glGenVertexArrays");
    if (!glGenVertexArrays) {
        return false;
    }

    glGetAttribLocation = (PFNGLGETATTRIBLOCATIONPROC) wglGetProcAddress("glGetAttribLocation");
    if (!glGetAttribLocation) {
        return false;
    }

    glGetProgramInfoLog = (PFNGLGETPROGRAMINFOLOGPROC) wglGetProcAddress("glGetProgramInfoLog");
    if (!glGetProgramInfoLog) {
        return false;
    }

    
        glGetProgramiv = (PFNGLGETPROGRAMIVPROC)wglGetProcAddress("glGetProgramiv");
        if(!glGetProgramiv)
        {
                return false;
        }

        glGetShaderInfoLog = (PFNGLGETSHADERINFOLOGPROC)wglGetProcAddress("glGetShaderInfoLog");
        if(!glGetShaderInfoLog)
        {
                return false;
        }

        glGetShaderiv = (PFNGLGETSHADERIVPROC)wglGetProcAddress("glGetShaderiv");
        if(!glGetShaderiv)
        {
                return false;
        }

        glLinkProgram = (PFNGLLINKPROGRAMPROC)wglGetProcAddress("glLinkProgram");
        if(!glLinkProgram)
        {
                return false;
        }

        glShaderSource = (PFNGLSHADERSOURCEPROC)wglGetProcAddress("glShaderSource");
        if(!glShaderSource)
        {
                return false;
        }

        glUseProgram = (PFNGLUSEPROGRAMPROC)wglGetProcAddress("glUseProgram");
        if(!glUseProgram)
        {
                return false;
        }

        glVertexAttribPointer = (PFNGLVERTEXATTRIBPOINTERPROC)wglGetProcAddress("glVertexAttribPointer");
        if(!glVertexAttribPointer)
        {
                return false;
        }

        glBindAttribLocation = (PFNGLBINDATTRIBLOCATIONPROC)wglGetProcAddress("glBindAttribLocation");
        if(!glBindAttribLocation)
        {
                return false;
        }

        glGetUniformLocation = (PFNGLGETUNIFORMLOCATIONPROC)wglGetProcAddress("glGetUniformLocation");
        if(!glGetUniformLocation)
        {
                return false;
        }

        glUniformMatrix4fv = (PFNGLUNIFORMMATRIX4FVPROC)wglGetProcAddress("glUniformMatrix4fv");
        if(!glUniformMatrix4fv)
        {
                return false;
        }

        glActiveTexture = (PFNGLACTIVETEXTUREPROC)wglGetProcAddress("glActiveTexture");
        if(!glActiveTexture)
        {
                return false;
        }

        glUniform1i = (PFNGLUNIFORM1IPROC)wglGetProcAddress("glUniform1i");
        if(!glUniform1i)
        {
                return false;
        }

        glGenerateMipmap = (PFNGLGENERATEMIPMAPPROC)wglGetProcAddress("glGenerateMipmap");
        if(!glGenerateMipmap)
        {
                return false;
        }

        glDisableVertexAttribArray = (PFNGLDISABLEVERTEXATTRIBARRAYPROC)wglGetProcAddress("glDisableVertexAttribArray");
        if(!glDisableVertexAttribArray)
        {
                return false;
        }

        glUniform3fv = (PFNGLUNIFORM3FVPROC)wglGetProcAddress("glUniform3fv");
        if(!glUniform3fv)
        {
                return false;
        }

        glUniform4fv = (PFNGLUNIFORM4FVPROC)wglGetProcAddress("glUniform4fv");
        if(!glUniform4fv)
        {
                return false;
        }

        return true;
}

void FinalizeOpenGL(HWND hwnd)
{
    // release the rendering context.
    if (g_renderingContext) {
        wglMakeCurrent(NULL, NULL);
        wglDeleteContext(g_renderingContext);
        g_renderingContext = 0;
    }

    // release the device context
    if (g_deviceContext) {
        ReleaseDC(hwnd, g_deviceContext);
        g_deviceContext = 0;
    }
}

void GetVideoCardInfo(char* cardName)
{
    strcpy_s(cardName, 128, g_videoCardDescription);
    return;
}

bool InitializeExtensions(HWND hwnd)
{
    HDC deviceContext;
    PIXELFORMATDESCRIPTOR pixelFormat = {0};
    pixelFormat.nSize = sizeof(PIXELFORMATDESCRIPTOR);
    pixelFormat.nVersion = 1;
    pixelFormat.dwFlags = PFD_DRAW_TO_WINDOW 
        | PFD_SUPPORT_OPENGL
        | PFD_GENERIC_ACCELERATED
        ;
    pixelFormat.iPixelType = PFD_TYPE_RGBA;
    pixelFormat.cColorBits = 32;
    pixelFormat.cDepthBits = 24;
    pixelFormat.iLayerType = PFD_MAIN_PLANE;

    int error;
    HGLRC renderContext;
    bool result;

    // get the device context for this window
    deviceContext = GetDC(hwnd);
    if (!deviceContext) {
        return false;
    }

    // set a temporary  default pixel format.
    error = SetPixelFormat(deviceContext, 1, &pixelFormat);
    if (error != 1)  {
        return false;
    }

    // Create a temporary rendering context
    renderContext = wglCreateContext(deviceContext);
    if (!renderContext) {
        return false;
    }

    // set the temporary rendering context as the current rendering context for this window
    error = wglMakeCurrent(deviceContext, renderContext);
    if (error !=1 ) {
        return false;
    }

    // Initalize the OpenGL extensions needed for this applicatino.
    // Note that a temporary rendering context was needed to do so
    result = LoadExtensionList();
    if (!result) {
        return false;
    }

    // Release the temporary rendering context now that the extensions have been loaded.
    wglMakeCurrent(NULL, NULL);
    wglDeleteContext(renderContext);
    renderContext = NULL;

    // release the device context for this window
    ReleaseDC(hwnd, deviceContext);
    deviceContext = 0;

    return true;
}

void OutputShaderErrorMessage(HWND hwnd, unsigned int shaderId, const char* shaderFilename)
{
    int logSize;
    int i;
    char* infoLog;
    std::ofstream fout;
    wchar_t newString[128];
    unsigned int error;
    size_t  convertedChars;


    // Get the size of the string containing the information log for the failed shader compilation message.
    glGetShaderiv(shaderId, GL_INFO_LOG_LENGTH, &logSize);
    
    // Increment the size by one to handle also the null terminator.
    logSize++;

    // Create a char buffer to hold the info log.
    infoLog = new char[logSize];
    if(!infoLog)
    {
        return;
    }

    // Now retrieve the info log.
    glGetShaderInfoLog(shaderId, logSize, NULL, infoLog);

    // Open a file to write the error message to.
    fout.open("shader-error.txt");

    // Write out the error message.
    for (i = 0; i < logSize; i++)
    {
        fout << infoLog[i];
    }

    // close file
    fout.close();

     // Convert the shader filename to a wide character string.
     error = mbstowcs_s(&convertedChars, newString, 128, shaderFilename, 128);
     if (error != 0) 
     {
         return;
     }

      // Pop a message up on the screen to notify the user to check the text file for compile errors.
      MessageBoxW(hwnd, L"Error compiling shader.  Check shader-error.txt for message.", newString, MB_OK);
      return;
}

void OutputLinkerErrorMessage(HWND hwnd, unsigned int programId)
{

    int logSize, i;
    char* infoLog;
    std::ofstream fout;

    // Get the size of the string containing the information log for the failed shader compilation message.
    glGetProgramiv(programId, GL_INFO_LOG_LENGTH, &logSize);

    // Increment the size by one to handle also the null terminator.
    logSize++;

    // Create a char buffer to hold the info log.
    infoLog = new char[logSize];
    if (!infoLog) {
        return;
    }

    // Now retrieve the info log.
    glGetProgramInfoLog(programId, logSize, NULL, infoLog);

    // Open a file to write the error message to.
    fout.open("linker-error.txt");

    // Write out the error message.
    for (i = 0; i < logSize; i++)
    {
        fout << infoLog[i];
    }

    // close file
    fout.close();

    // Pop a message up on the screen to notify the user to check the text file for linker errors.
    MessageBoxW(hwnd, L"Error compiling linker.  Check linker-error.txt for message.", L"Linker Error", MB_OK);
}

char* LoadShaderSourceFile(const char* filename)
{
    std::ifstream fin;
    int fileSize;
    char input;
    char* buffer;

    // Open the shader source file.
    fin.open(filename);

    // If it could not open the file then exit.
    if(fin.fail())
    {
        return 0;
    }

    // Initialize the size of the file.
    fileSize = 0;

    // Read the first element of the file.
    fin.get(input);

    // Count the number of elements in the text file.
    while(!fin.eof())
    {
        fileSize++;
        fin.get(input);
    }

    // Close the file for now.
    fin.close();

    // Initialize the buffer to read the shader source file into.
    buffer = new char[fileSize+1];
    if(!buffer)
    {
        return 0;
    }

    // Open the shader source file again.
    fin.open(filename);

    // Read the shader text file into the buffer as a block.
    fin.read(buffer, fileSize);

    // Close the file.
    fin.close();

    // Null terminate the buffer.
    buffer[fileSize] = '\0';

    return buffer;
}

bool InitializeShader(HWND hwnd, const char* vsFilename, const char* fsFilename)
{
    const char* vertexShaderBuffer;
    const char* fragmentShaderBuffer;
    int status;

    // Load the vertex shader source file into a text buffer.
    vertexShaderBuffer = LoadShaderSourceFile(vsFilename);
    if(!vertexShaderBuffer)
    {
        return false;
    }

    // Load the fragment shader source file into a text buffer.
    fragmentShaderBuffer = LoadShaderSourceFile(fsFilename);
    if(!fragmentShaderBuffer)
    {
        return false;
    }

    // Create a vertex and fragment shader object.
    g_vertexShader = glCreateShader(GL_VERTEX_SHADER);
    g_fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);

    // Copy the shader source code strings into the vertex and fragment shader objects.
    glShaderSource(g_vertexShader, 1, &vertexShaderBuffer, NULL);
    glShaderSource(g_fragmentShader, 1, &fragmentShaderBuffer, NULL);

    // Release the vertex and fragment shader buffers.
    delete [] vertexShaderBuffer;
    vertexShaderBuffer = nullptr;

    delete [] fragmentShaderBuffer;
    fragmentShaderBuffer = nullptr;

    // Compile the shaders.
    glCompileShader(g_vertexShader);
    glCompileShader(g_fragmentShader);

    // Check to see if the vertex shader compiled successfully.
    glGetShaderiv(g_vertexShader, GL_COMPILE_STATUS, &status);
    if(status != 1)
    {
        // If it did not compile then write the syntax error message out to a text file for review.
        OutputShaderErrorMessage(hwnd, g_vertexShader, vsFilename);
        return false;
    }

    // Check to see if the fragment shader compiled successfully.
    glGetShaderiv(g_fragmentShader, GL_COMPILE_STATUS, &status);
    if(status != 1)
    {
        // If it did not compile then write the syntax error message out to a text file for review.
        OutputShaderErrorMessage(hwnd, g_fragmentShader, fsFilename);
        return false;
    }

    // Create a shader program object.
    g_shaderProgram = glCreateProgram();

    // Attach the vertex and fragment shader to the program object.
    glAttachShader(g_shaderProgram, g_vertexShader);
    glAttachShader(g_shaderProgram, g_fragmentShader);

    // Bind the shader input variables.
    glBindAttribLocation(g_shaderProgram, 0, "inputPosition");
    glBindAttribLocation(g_shaderProgram, 1, "inputColor");

    // Link the shader program.
    glLinkProgram(g_shaderProgram);

    // Check the status of the link.
    glGetProgramiv(g_shaderProgram, GL_LINK_STATUS, &status);
    if(status != 1)
    {
        // If it did not link then write the syntax error message out to a text file for review.
        OutputLinkerErrorMessage(hwnd, g_shaderProgram);
        return false;
    }

    return true;
}


void ShutdownShader()
{
    // Detach the vertex and fragment shaders from the program.
    glDetachShader(g_shaderProgram, g_vertexShader);
    glDetachShader(g_shaderProgram, g_fragmentShader);

    // Delete the vertex and fragment shaders.
    glDeleteShader(g_vertexShader);
    glDeleteShader(g_fragmentShader);

    // Delete the shader program.
    glDeleteProgram(g_shaderProgram);
}

bool SetShaderParameters(float* worldMatrix, float* viewMatrix, float* projectionMatrix)
{
    unsigned int location;

    // Set the world matrix in the vertex shader.
    location = glGetUniformLocation(g_shaderProgram, "worldMatrix");
    if (location ==-1) {
        return  false;
    }

    glUniformMatrix4fv(location, 1, false, worldMatrix);

    // Set the view matrix in the vertex shader.
    location = glGetUniformLocation(g_shaderProgram, "viewMatrix");
    if(location == -1)
    {
        return false;
    }
    glUniformMatrix4fv(location, 1, false, viewMatrix);

    // Set the projection matrix in the vertex shader.
    location = glGetUniformLocation(g_shaderProgram, "projectionMatrix");
    if(location == -1)
    {
        return false;
    }
    glUniformMatrix4fv(location, 1, false, projectionMatrix);

    return true;
}

bool InitializeBuffers()
{
    // Create a cube
    //    +y
    //    v3----- v0
    //   /|      /|
    //  v2------v1|
    //  | | +z  | |
    //  | |v7---|-|v4   +x
    //  |/      |/
    //  v6------v5
    // 


    VertexType vertices[] = {
        {{  1.0f,  1.0f,  1.0f }, { 1.0f, 0.0f, 0.0f }}, // v0
        {{  1.0f,  1.0f, -1.0f }, { 0.0f, 1.0f, 0.0f }}, // v1
        {{ -1.0f,  1.0f, -1.0f }, { 0.0f, 0.0f, 1.0f }}, // v2
        {{ -1.0f,  1.0f,  1.0f }, { 1.0f, 1.0f, 0.0f }}, // v3
        {{  1.0f, -1.0f,  1.0f }, { 1.0f, 0.0f, 1.0f }}, // v4
        {{  1.0f, -1.0f, -1.0f }, { 0.0f, 1.0f, 1.0f }}, // v5
        {{ -1.0f, -1.0f, -1.0f }, { 0.5f, 1.0f, 0.5f }}, // v6
        {{ -1.0f, -1.0f,  1.0f }, { 1.0f, 0.5f, 1.0f }}, // v7
    };

    uint16_t indices[] = 
    { 
        1, 2, 3, 3, 2, 6, 
        6, 7, 3, 3, 0, 1, 
        0, 3, 7, 7, 6, 4, 
        4, 6, 5, 0, 7, 4, 
        1, 0, 4, 1, 4, 5, 
        2, 1, 5, 2, 5, 6 
    };

    // Set the number of vertices in the vertex array.
    g_vertexCount = sizeof(vertices) / sizeof(VertexType);

    // Set the number of indices in the index array.
    g_indexCount = sizeof(indices) / sizeof(uint16_t);

    // Allocate an OpenGL vertex array object.
    glGenVertexArrays(1, &g_vertexArrayId);

    // Bind the vertex array object to store all the buffers and vertex attributes we create here.
    glBindVertexArray(g_vertexArrayId);

    // Generate an ID for the vertex buffer.
    glGenBuffers(1, &g_vertexBufferId);


    // Bind the vertex buffer and load the vertex (position and color) data into the vertex buffer.
    glBindBuffer(GL_ARRAY_BUFFER, g_vertexBufferId);
    glBufferData(GL_ARRAY_BUFFER, g_vertexCount * sizeof(VertexType), vertices, GL_STATIC_DRAW);

    // Enable the two vertex array attributes.
    glEnableVertexAttribArray(0);  // Vertex position.
    glEnableVertexAttribArray(1);  // Vertex color.

    // Specify the location and format of the position portion of the vertex buffer.
    glBindBuffer(GL_ARRAY_BUFFER, g_vertexBufferId);
    glVertexAttribPointer(0, 3, GL_FLOAT, false, sizeof(VertexType), 0);

    // Specify the location and format of the color portion of the vertex buffer.
    glBindBuffer(GL_ARRAY_BUFFER, g_vertexBufferId);
    glVertexAttribPointer(1, 3, GL_FLOAT, false, sizeof(VertexType), (char*)NULL + (3 * sizeof(float)));

    // Generate an ID for the index buffer.
    glGenBuffers(1, &g_indexBufferId);


    // Bind the index buffer and load the index data into it.
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, g_indexBufferId);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, g_indexCount* sizeof(uint16_t), indices, GL_STATIC_DRAW);

    return true;
}

void ShutdownBuffers()
{
    // Disable the two vertex array attributes.
    glDisableVertexAttribArray(0);
    glDisableVertexAttribArray(1);

    // Release the vertex buffer.
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glDeleteBuffers(1, &g_vertexBufferId);

    // Release the index buffer.
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
    glDeleteBuffers(1, &g_indexBufferId);

    // Release the vertex array object.
    glBindVertexArray(0);
    glDeleteVertexArrays(1, &g_vertexArrayId);
}


void RenderBuffers()
{
    // Bind the vertex array object that stored all the information about the vertex and index buffers.
    glBindVertexArray(g_vertexArrayId);

    // Render the vertex buffer using the index buffer.
    glDrawElements(GL_TRIANGLES, g_indexCount, GL_UNSIGNED_SHORT, 0);
    return;
}

void CalculateCameraPosition()
{
    VectorType up, position, lookAt;
    float yaw, pitch, roll;
    float rotationMatrix[9];


    // Setup the vector that points upwards.
    up.x = 0.0f;
    up.y = 1.0f;
    up.z = 0.0f;

    // Setup the position of the camera in the world.
    position.x = g_positionX;
    position.y = g_positionY;
    position.z = g_positionZ;

    // Setup where the camera is looking by default.
    lookAt.x = 0.0f;
    lookAt.y = 0.0f;
    lookAt.z = 1.0f;

    // Set the yaw (Y axis), pitch (X axis), and roll (Z axis) rotations in radians.
    pitch = g_rotationX * 0.0174532925f;
    yaw   = g_rotationY * 0.0174532925f;
    roll  = g_rotationZ * 0.0174532925f;

    // Create the rotation matrix from the yaw, pitch, and roll values.
    MatrixRotationYawPitchRoll(rotationMatrix, yaw, pitch, roll);

    // Transform the lookAt and up vector by the rotation matrix so the view is correctly rotated at the origin.
    TransformCoord(lookAt, rotationMatrix);
    TransformCoord(up, rotationMatrix);

    // Translate the rotated camera position to the location of the viewer.
    lookAt.x = position.x + lookAt.x;
    lookAt.y = position.y + lookAt.y;
    lookAt.z = position.z + lookAt.z;

    // Finally create the view matrix from the three updated vectors.
    BuildViewMatrix(position, lookAt, up, g_viewMatrix);
}

void Draw()
{
    static float rotateAngle = 0.0f;

    // Set the color to clear the screen to.
    glClearColor(0.2f, 0.3f, 0.4f, 1.0f);
    // Clear the screen and depth buffer.
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    // Update world matrix to rotate the model
    rotateAngle += PI / 120;
    float rotationMatrixY[16];
    float rotationMatrixZ[16];
    MatrixRotationY(rotationMatrixY, rotateAngle);
    MatrixRotationZ(rotationMatrixZ, rotateAngle);
    MatrixMultiply(g_worldMatrix, rotationMatrixZ, rotationMatrixY);

    // Generate the view matrix based on the camera's position.
    CalculateCameraPosition();

    // Set the color shader as the current shader program and set the matrices that it will use for rendering.
    glUseProgram(g_shaderProgram);
    SetShaderParameters(g_worldMatrix, g_viewMatrix, g_projectionMatrix);

    // Render the model using the color shader.
    RenderBuffers();

    // Present the back buffer to the screen since rendering is complete.
    SwapBuffers(g_deviceContext);
}


// the WindowProc function prototype
LRESULT CALLBACK WindowProc(HWND hWnd,
                            UINT message,
                            WPARAM wParam,
                            LPARAM lParam);

// the entry point for any Windows program
int WINAPI WinMain(HINSTANCE hInstance,
                   HINSTANCE hPreInstance,
                   LPSTR lpCmdLine,
                   int nCmdShow){ 
                
    // the handle for the window, filled by a function
    HWND hWnd;
    // this struct holds information for the window class
    WNDCLASSEX wc;

    // clear out the window class for use
    ZeroMemory(&wc, sizeof(WNDCLASSEX));

    // fill in the struct with the needed information
    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
    wc.lpfnWndProc = DefWindowProc;
    wc.hInstance = hInstance;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)COLOR_WINDOW;
    const wchar_t* tempName = L"Temporary";
    wc.lpszClassName = tempName;

    // register the window class
    RegisterClassEx(&wc);

    // create the window and use the result as the handle
    hWnd = CreateWindowEx(WS_EX_APPWINDOW,
                          tempName,   // name of the window
                          tempName, // title of the window
                          WS_OVERLAPPEDWINDOW,  // window style
                          0,                  // x positon of the window    
                          0,                  // y positon of the window
                          640,                  // width of the window
                          480,                  // height of the window
                          NULL,                 // we have no parent window, NULL
                          NULL,                 // we aren't using menus, null
                          hInstance,            // application handle
                          NULL);                // used with multiple windows, NULL
    
    // display the window on the screen
    ShowWindow(hWnd, SW_HIDE);
    
    InitializeExtensions(hWnd);

    DestroyWindow(hWnd);
    hWnd = NULL;

    // clear out the window class for use
    ZeroMemory(&wc, sizeof(WNDCLASSEX));

    // fill in the struct with the needed information
    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
    wc.lpfnWndProc = WindowProc;
    wc.hInstance = hInstance;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)COLOR_WINDOW;
    wc.lpszClassName = szAppName;

    // register the window class
    RegisterClassEx(&wc);

    // create the window and use the result as the handle
    hWnd = CreateWindowEx(WS_EX_APPWINDOW,
                          szAppName,                // name of the window
                          szAppName,                // title of the window
                          WS_OVERLAPPEDWINDOW,      // window style
                          300,                      // x positon of the window    
                          300,                      // y positon of the window
                          960,                      // width of the window
                          540,                      // height of the window
                          NULL,                     // we have no parent window, NULL
                          NULL,                     // we aren't using menus, null
                          hInstance,                // application handle
                          NULL);  

    bool success = InitializeOpenGL(hWnd, 960, 540, SCREEN_DEPTH, SCREEN_NEAR, true);
    std::wstring isSuc = success ? L"success" : L"fail";
    std::wstring logStr = L"InitializeOpenGL " + isSuc + L'\n';

    OutputDebugStringW(logStr.c_str());
    // display the window on the screen
    ShowWindow(hWnd, nCmdShow);
    SetForegroundWindow(hWnd);

    success = InitializeShader(hWnd, VS_SHADER_SOURCE_FILE, PS_SHADER_SOURCE_FILE);

    isSuc = success ? L"success" : L"fail";
    logStr = L"InitializeShader " + isSuc + L'\n';
    OutputDebugStringW(logStr.c_str());
    
    success = InitializeBuffers();

    isSuc = success ? L"success" : L"fail";
    logStr = L"InitializeBuffers " + isSuc + L'\n';
    OutputDebugStringW(logStr.c_str());

    // enter the main loop:

    // the struct holds Windows event messages
    MSG msg;

    // wait for the next message in the queue, store the result in "msg"
    while(GetMessageW(&msg, NULL, 0, 0)) {
        // translate keystroke messages into the right format
        TranslateMessage(&msg);

        // send the message to the WindowProc function
        DispatchMessageW(&msg);
    }

    ShutdownBuffers();
    ShutdownShader();
    FinalizeOpenGL(hWnd);

    // return this part of the WM_QUIT message to Windows
    return int(msg.wParam);
}

LRESULT CALLBACK WindowProc(HWND hWnd,
                            UINT message,
                            WPARAM wParam,
                            LPARAM lParam){
    switch (message)
    {
        // sort through and find what code to run for the message given
        case WM_CREATE: {
        }
        break;
        case WM_PAINT:
            {
                Draw();
                OutputDebugStringW(L"Draw()\n");
            }
            break;
        // this message is read when the window is closed
        case WM_DESTROY:
            {
                // close the application entirely
                PostQuitMessage(0);
                return 0;
            }
            break;

    }

    // Handle any message the switch statement didn't 
    return DefWindowProc(hWnd, message, wParam, lParam);
}